package com.cpkf.util.iTextPDF;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.itextpdf.text.Anchor;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chapter;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.ListItem;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.Section;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfWriter;

/**
 * PDF basic function, include chapter, section, subsection, anchor, list, directory .etc
 * 
 * @author Hyman Hu
 * @since 2014-6-23 Copyright SHOP.COM: all rights reserved worldwide
 */
public class PDFCreator {

	/*
	 * chapter counter
	 */
	public int chapterNumber = 1;
	/*
	 * abchor counter
	 */
	public int anchorNumber = 0;
	public PdfWriter pdfWriter;
	public HeaderFooterEvent headerFooterEvent;
	public ChapterSectionEvent chapterSectionEvent;
	
	// PDF default information
	public String subjectName = "FAMOS";
	public String author = "Administrator";
	public String creator = "Administrator";
	public String keyWord = "shop.com famos amos";
	public String headerContent = "Create by @Shop.Com";
	
	// PDF's properties
	public float marginTop = 50f;
	public float marginRight = 50f;
	public float marginDown = 50f;
	public float marginLeft = 50f;
	public BaseColor baseColor = new BaseColor(0xFF, 0xFF, 0xDE);
	public Rectangle baseRectangle = new Rectangle(PageSize.A4);
	public BaseColor headerColor = BaseColor.CYAN;
	public BaseColor borderColor = BaseColor.GREEN;
	public Rectangle headerRectangle = new Rectangle(36, 54, 559, 800);
	
	// font
	public Font baseFont = initFont(12, Font.NORMAL, false, BaseColor.BLACK);
	public Font titleFont = initFont(30, Font.BOLD, false, BaseColor.BLACK);
	public Font chapterFont = initFont(18, Font.BOLD, false, new BaseColor(255, 0, 0));
	public Font sectionFont = initFont(16, Font.NORMAL, false, new BaseColor(0, 0, 255));
	public Font subsectionFont = initFont(14, Font.NORMAL, false, new BaseColor(0, 64, 64));
	public Font anchorFont = initFont(12, Font.NORMAL, true, new BaseColor(0, 0, 255));
	public Font tableHeaderFont = initFont(12, Font.BOLD, false, new BaseColor(0, 0, 255));
	
	/*
	 * the flag of page rule, directory page or content page
	 */
	public static boolean IS_CONTENT_PAGE = true;
	/*
	 * this contain all chapter/section/subsection link info
	 */
	public static Map<String, String> ANCHOR_MAP = new HashMap<String, String>();

	public void setChapterNumber(int chapterNumber) {
		this.chapterNumber = chapterNumber;
	}

	public void setAnchorNumber(int anchorNumber) {
		this.anchorNumber = anchorNumber;
	}

	public PdfWriter getPdfWriter() {
		return pdfWriter;
	}

	public void setPdfWriter(PdfWriter pdfWriter) {
		this.pdfWriter = pdfWriter;
	}

	public HeaderFooterEvent getHeaderFooterEvent() {
		return headerFooterEvent;
	}

	public void setHeaderFooterEvent(HeaderFooterEvent headerFooterEvent) {
		this.headerFooterEvent = headerFooterEvent;
	}

	public ChapterSectionEvent getChapterSectionEvent() {
		return chapterSectionEvent;
	}

	public void setChapterSectionEvent(ChapterSectionEvent chapterSectionEvent) {
		this.chapterSectionEvent = chapterSectionEvent;
	}

	public void setSubjectName(String subjectName) {
		this.subjectName = subjectName;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public void setCreator(String creator) {
		this.creator = creator;
	}

	public void setKeyWord(String keyWord) {
		this.keyWord = keyWord;
	}

	public void setHeaderContent(String headerContent) {
		this.headerContent = headerContent;
	}

	public void setHeaderRectangle(Rectangle headerRectangle) {
		this.headerRectangle = headerRectangle;
	}

	public void setBaseFont(Font baseFont) {
		this.baseFont = baseFont;
	}

	public void setTitleFont(Font titleFont) {
		this.titleFont = titleFont;
	}

	public void setChapterFont(Font chapterFont) {
		this.chapterFont = chapterFont;
	}

	public void setSectionFont(Font sectionFont) {
		this.sectionFont = sectionFont;
	}

	public void setSubsectionFont(Font subsectionFont) {
		this.subsectionFont = subsectionFont;
	}

	public void setAnchorFont(Font anchorFont) {
		this.anchorFont = anchorFont;
	}

	public void setTableHeaderFont(Font tableHeaderFont) {
		this.tableHeaderFont = tableHeaderFont;
	}
	
	public void setMarginTop(float marginTop) {
		this.marginTop = marginTop;
	}

	public void setMarginRight(float marginRight) {
		this.marginRight = marginRight;
	}

	public void setMarginDown(float marginDown) {
		this.marginDown = marginDown;
	}

	public void setMarginLeft(float marginLeft) {
		this.marginLeft = marginLeft;
	}

	public void setBaseColor(BaseColor baseColor) {
		this.baseColor = baseColor;
	}

	public void setBaseRectangle(Rectangle baseRectangle) {
		this.baseRectangle = baseRectangle;
	}
	
	public void setHeaderColor(BaseColor headerColor) {
		this.headerColor = headerColor;
	}

	public void setBorderColor(BaseColor borderColor) {
		this.borderColor = borderColor;
	}

	public PDFCreator() {
		IS_CONTENT_PAGE = true;
		ANCHOR_MAP = new HashMap<String, String>();
	}

	public PDFCreator(String subjectName, String author, String creator, String keyWord) {
		this.subjectName = subjectName;
		this.author = author;
		this.creator = creator;
		this.keyWord = keyWord;
		
		IS_CONTENT_PAGE = true;
		ANCHOR_MAP = new HashMap<String, String>();
	}

	public PDFCreator(Rectangle baseRectangle, float marginTop, float marginRight, float marginDown, float marginLeft) {
		this.marginTop = marginTop;
		this.marginRight = marginRight;
		this.marginDown = marginDown;
		this.marginLeft = marginLeft;
		this.baseRectangle = baseRectangle;
		
		IS_CONTENT_PAGE = true;
		ANCHOR_MAP = new HashMap<String, String>();
	}

	public PDFCreator(Rectangle baseRectangle, float marginTop, float marginRight, float marginDown, 
			float marginLeft, BaseColor baseColor, Font baseFont) {
		this.marginTop = marginTop;
		this.marginRight = marginRight;
		this.marginDown = marginDown;
		this.marginLeft = marginLeft;
		this.baseColor = baseColor;
		this.baseRectangle = baseRectangle;
		this.baseFont = baseFont;
		
		IS_CONTENT_PAGE = true;
		ANCHOR_MAP = new HashMap<String, String>();
	}

	/**
	 * initialization Font
	 * 
	 * @param fontSize			font size
	 * @param isBold			judge whether bold
	 * @param isUnderLine		judge whether under line
	 * @param color				font solor
	 * @return
	 */
	public Font initFont(int fontSize, int fontStyle, boolean isUnderLine, BaseColor color) {
		BaseFont baseFont;
		Font font = null;
		try {
			baseFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", false);
			font = new Font(baseFont, fontSize, fontStyle, color);
			if (isUnderLine) {
				font.setStyle(Font.UNDERLINE);
			}
		} catch (DocumentException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		return font;
	}
	
	/**
	 * initialization document
	 * 
	 * @param title						title of PDF
	 * @param out						OutputStream of PDF
	 * @return Document					this is basis element for PDF
	 * @throws DocumentException
	 */
	public Document initDocument(String title, OutputStream out) throws DocumentException {
		baseRectangle.setBackgroundColor(baseColor);
		Document document = new Document(baseRectangle, marginLeft, marginRight, marginTop, marginDown);

		pdfWriter = PdfWriter.getInstance(document, out);
		// pdfWriter.setViewerPreferences(PdfWriter.PageLayoutSinglePage);
		document.open();

		document.addTitle(title);
		document.addSubject(subjectName);
		document.addAuthor(author);
		document.addCreator(creator);
		document.addKeywords(keyWord);

		headerFooterEvent = new HeaderFooterEvent(headerRectangle, headerContent);
		pdfWriter.setPageEvent(headerFooterEvent);

		pdfWriter.setLinearPageMode();
		chapterSectionEvent = new ChapterSectionEvent();
		chapterSectionEvent.setChapterFont(chapterFont);
		chapterSectionEvent.setSectionFont(sectionFont);
		chapterSectionEvent.setSubsectionFont(subsectionFont);
		pdfWriter.setPageEvent(chapterSectionEvent);

		return document;
	}

	/**
	 * close document
	 * 
	 * @param document		document of PDF
	 */
	public void closeDocument(Document document) {
		if (document != null) {
			document.close();
		}
	}

	/**
	 * next line
	 * 
	 * @param document				document of PDF
	 * @throws DocumentException
	 */
	public void createSeparatorLine(Document document) throws DocumentException {
		document.add(new Paragraph("\n\r"));
	}

	/**
	 * create chapter in document
	 * 
	 * @param document					document of PDF
	 * @param chapterTitle				chapter title name
	 * @return Chapter					document not add chapter this time, when create chapter finished, then document must add it.
	 * @throws DocumentException
	 */
	public Chapter createChapter(Document document, String chapterTitle) throws DocumentException {
		if (document == null || chapterTitle.isEmpty()) {
			return null;
		}

		String destinationString = String.format("%s%s", chapterTitle, anchorNumber++);
		Chapter chapter = new Chapter(new Paragraph(
				new Chunk(chapterTitle, chapterFont).setLocalDestination(destinationString)), chapterNumber++);
		chapter.setBookmarkOpen(false);
		chapter.setBookmarkTitle(chapterTitle);
		chapter.setTriggerNewPage(false);

		ANCHOR_MAP.put(chapter.getTitle().getContent(), destinationString);

		return chapter;
	}
	
	/**
	 * add section in chapter
	 * 
	 * @param chapter				chapter element of PDF
	 * @param sectionTitle			section title name
	 * @return Section				section have added in chapter this time
	 * @throws DocumentException
	 */
	public Section addSection(Chapter chapter, String sectionTitle) throws DocumentException {
		if (chapter == null) {
			return null;
		}

		String destinationString = String.format("%s%s", sectionTitle, anchorNumber++);
		Section section = chapter.addSection(new Paragraph(new Chunk(sectionTitle, sectionFont)
				.setLocalDestination(destinationString)));
		section.setIndentation(10);
		section.setIndentationLeft(10);
		section.setBookmarkOpen(false);
		section.setBookmarkTitle(sectionTitle);
		section.setNumberStyle(Section.NUMBERSTYLE_DOTTED_WITHOUT_FINAL_DOT);

		ANCHOR_MAP.put(section.getTitle().getContent(), destinationString);

		return section;
	}
	
	/**
	 * add subsection in section
	 * 
	 * @param section				section element of PDF
	 * @param subsectionTitle		subsection title name
	 * @return Section				subsection have added in suction this time
	 * @throws DocumentException
	 */
	public Section addSubsection(Section section, String subsectionTitle) throws DocumentException {
		if (section == null) {
			return null;
		}

		String destinationString = String.format("%s%s", subsectionTitle, anchorNumber++);
		Section subsection = section.addSection(new Paragraph(new Chunk(subsectionTitle, subsectionFont)
				.setLocalDestination(destinationString)));
		subsection.setIndentation(10);
		subsection.setIndentationLeft(10);
		subsection.setBookmarkOpen(false);
		subsection.setBookmarkTitle(subsectionTitle);
		subsection.setNumberStyle(Section.NUMBERSTYLE_DOTTED_WITHOUT_FINAL_DOT);

		ANCHOR_MAP.put(subsection.getTitle().getContent(), destinationString);

		return subsection;
	}
	
	/**
	 * create anchor
	 * 
	 * @param anchorName		anchor name
	 * @param anchorUrl			anchor url, it can be local or remote
	 * @return Anchor			anchor not added this time
	 */
	public Anchor createAnchor(String anchorName, String anchorUrl) {
		if (anchorName.isEmpty()) {
			return null;
		}

		Anchor anchor = new Anchor(new Paragraph(anchorName, anchorFont));
		anchor.setName(anchorName);
		if (!anchorUrl.isEmpty()) {
			anchor.setReference(anchorUrl);
		}

		return anchor;
	}
	
	/**
	 * create list
	 * 
	 * @param isOrder					judge order in list
	 * @param originList				the origin data for list
	 * @return List						list element of PDF, not added this time
	 * @throws DocumentException
	 */
	public com.itextpdf.text.List createList(boolean isOrder, List<String> originList)
			throws DocumentException {
		com.itextpdf.text.List list = null;
		if (!originList.isEmpty()) {
			list = new com.itextpdf.text.List(isOrder, 20);
			for (int i = 0; i < originList.size(); i++) {
				list.add(new ListItem(originList.get(i)));
			}
		}
		return list;
	}
	
	/**
	 * create table, customize the headers of table
	 * 
	 * @param headers		headers of table
	 * @param jSONArray		the origin data for table
	 * @return PdfPTable	the element of PDF, not added this time
	 * @throws JSONException 
	 */
	public PdfPTable createTable(List<String> headers, JSONArray jSONArray) throws JSONException {
		if (headers.isEmpty()) {
			return null;
		}
		
		PdfPTable table = new PdfPTable(headers.size());
		table.setHorizontalAlignment(Element.ALIGN_CENTER);
		for (String header : headers) {
			PdfPCell cell = new PdfPCell(new Paragraph(header, tableHeaderFont));
			cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
			cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
			cell.setBackgroundColor(headerColor);
			cell.setBorderColor(borderColor);
			table.addCell(cell);
		}
		
		try {
			if (jSONArray != null) {
				for (int i = 0; i < jSONArray.length(); i++) {
					JSONObject jsonObject = jSONArray.getJSONObject(i);
					for (String header : headers) {
						Object value = jsonObject.get(header);
						PdfPCell cell = null;
						String textValue = "";
						if (value instanceof Boolean) {
							textValue = value.toString();
						} else if (value instanceof Date) {
							Date date = (Date) value;
							SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
							textValue = sdf.format(date);
						} else if (value instanceof byte[]) {
							byte[] bsValue = (byte[]) value;
							Image img = Image.getInstance(bsValue);
							cell = new PdfPCell(img);
						} else {
							textValue = value.toString();
						}
						
						if (cell == null) {
							cell = new PdfPCell(new Paragraph(textValue, baseFont));
						}
						
						cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
						cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
						cell.setBorderColor(BaseColor.GREEN);
						table.addCell(cell);
					}
				}
			}
		} catch (BadElementException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return table;
	}
	
	/**
	 * create table, the header is jsonObject keys
	 * 
	 * @param jSONArray			headers of table
	 * @return PdfPTable		the element of PDF, not added this time
	 * @throws JSONException 
	 */
	@SuppressWarnings("unchecked")
	public PdfPTable createTable(JSONArray jSONArray) throws JSONException {
		if (jSONArray == null) {
			return null;
		}
		
		List<String> headers = new ArrayList<String>();
		Iterator<String> iterator = jSONArray.getJSONObject(0).keys();
		while (iterator.hasNext()) {
			headers.add(iterator.next());
		}
		PdfPTable table = new PdfPTable(headers.size());
		table.setHorizontalAlignment(Element.ALIGN_CENTER);
		for (String header : headers) {
			PdfPCell cell = new PdfPCell(new Paragraph(header, tableHeaderFont));
			cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
			cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
			cell.setBackgroundColor(headerColor);
			cell.setBorderColor(borderColor);
			table.addCell(cell);
		}
		
		try {
			if (jSONArray != null) {
				for (int i = 0; i < jSONArray.length(); i++) {
					JSONObject jsonObject = jSONArray.getJSONObject(i);
					for (String header : headers) {
						Object value = jsonObject.get(header);
						PdfPCell cell = null;
						String textValue = "";
						if (value instanceof Boolean) {
							textValue = value.toString();
						} else if (value instanceof Date) {
							Date date = (Date) value;
							SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
							textValue = sdf.format(date);
						} else if (value instanceof byte[]) {
							byte[] bsValue = (byte[]) value;
							Image img = Image.getInstance(bsValue);
							cell = new PdfPCell(img);
						} else {
							textValue = value.toString();
						}
						
						if (cell == null) {
							cell = new PdfPCell(new Paragraph(textValue, baseFont));
						}
						
						cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
						cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
						cell.setBorderColor(BaseColor.GREEN);
						table.addCell(cell);
					}
				}
			}
		} catch (BadElementException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return table;
	}

	/**
	 * create table, customize the headers of table
	 * 
	 * @param result		the origin data for table
	 * @param headers		headers of table
	 * @return PdfPTable	the element of PDF, not added this time
	 */
	public PdfPTable createTable(List<String> headers, List<Map<String, Object>> result) {
		if (headers.isEmpty()) {
			return null;
		}
		
		PdfPTable table = new PdfPTable(headers.size());
		table.setHorizontalAlignment(Element.ALIGN_CENTER);
		for (String header : headers) {
			PdfPCell cell = new PdfPCell(new Paragraph(header, tableHeaderFont));
			cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
			cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
			cell.setBackgroundColor(headerColor);
			cell.setBorderColor(borderColor);
			table.addCell(cell);
		}
		
		try {
			if (!result.isEmpty()) {
				for (Map<String, Object> map : result) {
					for (String header : headers) {
						Object value = map.get(header);
						PdfPCell cell = null;
						String textValue = "";
						if (value instanceof Boolean) {
							textValue = value.toString();
						} else if (value instanceof Date) {
							Date date = (Date) value;
							SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
							textValue = sdf.format(date);
						} else if (value instanceof byte[]) {
							byte[] bsValue = (byte[]) value;
							Image img = Image.getInstance(bsValue);
							cell = new PdfPCell(img);
						} else {
							textValue = value.toString();
						}
						
						if (cell == null) {
							cell = new PdfPCell(new Paragraph(textValue, baseFont));
						}
						
						cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
						cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
						cell.setBorderColor(BaseColor.GREEN);
						table.addCell(cell);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return table;
	}
	
	/**
	 * create table
	 * 
	 * @param result     the origin data for table, table's headers is map keysets
	 * @return PdfPTable
	 */
	public PdfPTable createTable(List<Map<String, Object>> result) {
		if (result.isEmpty()) {
			return null;
		}

		List<String> headers = new ArrayList<String>();
		Iterator<String> iterator = result.get(0).keySet().iterator();
		while (iterator.hasNext()) {
			headers.add(iterator.next());
		}

		PdfPTable table = new PdfPTable(headers.size());
		table.setHorizontalAlignment(Element.ALIGN_CENTER);
		for (int i = 0; i < headers.size(); i++) {
			PdfPCell cell = new PdfPCell(new Paragraph(headers.get(i), tableHeaderFont));
			cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
			cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
			cell.setBackgroundColor(headerColor);
			cell.setBorderColor(borderColor);
			table.addCell(cell);
		}
		try {
			for (Map<String, Object> map : result) {
				for (iterator = map.keySet().iterator(); iterator.hasNext();) {
					Object value = map.get(iterator.next());
					PdfPCell cell = null;
					String textValue = null;
					if (value instanceof Boolean) {
						textValue = value.toString();
					} else if (value instanceof Date) {
						Date date = (Date) value;
						SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
						textValue = sdf.format(date);
					} else if (value instanceof byte[]) {
						byte[] bsValue = (byte[]) value;
						Image img = Image.getInstance(bsValue);
						cell = new PdfPCell(img);
					} else {
						textValue = value.toString();
					}

					if (textValue != null) {
						cell = new PdfPCell(new Paragraph(textValue, baseFont));
					}

					cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
					cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
					cell.setBorderColor(BaseColor.GREEN);
					table.addCell(cell);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return table;
	}

	/**
	 * create table, customize the headers of table
	 * 
	 * @param headers			headers of table
	 * @param dataset			the origin data for table
	 * @return PdfPTable
	 */
	public <T> PdfPTable createTable(List<String> headers, Collection<T> dataset) {
		if (headers.isEmpty()) {
			return null;
		}
		
		PdfPTable table = new PdfPTable(headers.size());
		table.setHorizontalAlignment(Element.ALIGN_CENTER);
		for (String header : headers) {
			PdfPCell cell = new PdfPCell(new Paragraph(header, tableHeaderFont));
			cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
			cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
			cell.setBackgroundColor(headerColor);
			cell.setBorderColor(borderColor);
			table.addCell(cell);
		}
		
		try {
			Iterator<T> it = dataset.iterator();
			while (it.hasNext()) {
				T t = it.next();
				for (String header : headers) {
					PdfPCell cell = null;
					String getMethodName = "get" + header.substring(0, 1).toUpperCase() + header.substring(1);
					Method getMethod = t.getClass().getMethod(getMethodName, new Class[] {});
					Object value = getMethod.invoke(t, new Object[] {});
					String textValue = "";
					if (value instanceof Boolean) {
						textValue = value.toString();
					} else if (value instanceof Date) {
						Date date = (Date) value;
						SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
						textValue = sdf.format(date);
					} else if (value instanceof byte[]) {
						byte[] bsValue = (byte[]) value;
						Image img = Image.getInstance(bsValue);
						cell = new PdfPCell(img);
					} else {
						textValue = value.toString();
					}

					if (cell == null) {
						cell = new PdfPCell(new Paragraph(textValue, baseFont));
					}

					cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
					cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
					cell.setBorderColor(BaseColor.GREEN);
					table.addCell(cell);
				}
			}
		} catch (BadElementException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return table;
		
	}
	
	/**
	 * create table
	 * 
	 * @param dataset    the origin data for table, header is property of T
	 * @return
	 */
	public <T> PdfPTable createTable(Collection<T> dataset) {
		if (dataset.isEmpty()) {
			return null;
		}

		List<String> headers = new ArrayList<String>();
		Iterator<T> iterator = dataset.iterator();
		if (iterator.hasNext()) {
			T t = iterator.next();
			Field[] fields = t.getClass().getDeclaredFields();
			for (Field field : fields) {
				headers.add(field.getName());
			}
		}
		PdfPTable table = new PdfPTable(headers.size());
		table.setHorizontalAlignment(Element.ALIGN_CENTER);
		table.setWidthPercentage(16 * headers.size());
		for (int i = 0; i < headers.size(); i++) {
			PdfPCell cell = new PdfPCell(new Paragraph(headers.get(i), tableHeaderFont));
			cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
			cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
			cell.setBackgroundColor(headerColor);
			cell.setBorderColor(borderColor);
			table.addCell(cell);
		}

		try {
			Iterator<T> it = dataset.iterator();
			while (it.hasNext()) {
				T t = it.next();
				Field[] fields = t.getClass().getDeclaredFields();
				for (Field field : fields) {
					PdfPCell cell = null;
					String fieldName = field.getName();
					String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
					Method getMethod = t.getClass().getMethod(getMethodName, new Class[] {});
					Object value = getMethod.invoke(t, new Object[] {});
					String textValue = null;
					if (value instanceof Boolean) {
						textValue = value.toString();
					} else if (value instanceof Date) {
						Date date = (Date) value;
						SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
						textValue = sdf.format(date);
					} else if (value instanceof byte[]) {
						byte[] bsValue = (byte[]) value;
						Image img = Image.getInstance(bsValue);
						cell = new PdfPCell(img);
					} else {
						textValue = value.toString();
					}

					if (textValue != null) {
						cell = new PdfPCell(new Paragraph(textValue, baseFont));
					}

					cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
					cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
					cell.setBorderColor(BaseColor.GREEN);
					table.addCell(cell);
				}
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (BadElementException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return table;
	}

	/**
	 * set table total width and column width
	 * 
	 * @param pdfPTable					element of PDF
	 * @param totalWidth				table's total width
	 * @param columnWidth				each column width of table
	 * @throws DocumentException 
	 */
	public void setTableWidth(PdfPTable pdfPTable, float totalWidth, float[] columnWidth) throws DocumentException {
		pdfPTable.setTotalWidth(totalWidth);
		if (columnWidth != null && columnWidth.length == pdfPTable.getNumberOfColumns()) {
			pdfPTable.setTotalWidth(columnWidth);
		}
		pdfPTable.setLockedWidth(true);
	}
	
	/**
	 * create image
	 * 
	 * @param imgURL			local or remote
	 * @param imgBytes			byte[] of image
	 * @param width				width of image
	 * @param height			height of image
	 * @return Image			element of PDF, not added this time
	 */
	@SuppressWarnings("null")
	public Image createImage(String imgURL, byte[] imgBytes, int width, int height) {
		Image image = null;
		if (!imgURL.isEmpty()) {
			String URLRegex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
			Pattern pattern = Pattern.compile(URLRegex);
			try {
				if (pattern.matcher(imgURL).matches()) {
					image = Image.getInstance(new URL(imgURL));
				} else {
					image = Image.getInstance(imgURL);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else if (imgBytes == null && imgBytes.length > 0) {
			try {
				image = Image.getInstance(imgBytes);
			} catch (BadElementException e) {
				e.printStackTrace();
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		if (image != null) {
			image.setAlignment(Image.LEFT);
			image.scaleAbsoluteWidth(width);
			image.scaleAbsoluteHeight(height);
		}

		return image;
	}

	/**
	 * add element in document
	 * 
	 * @param document					element of PDF
	 * @param element					element of PDF
	 * @throws DocumentException
	 */
	public void addContent(Document document, Element element) throws DocumentException {
		if (document != null && element != null) {
			document.add(element);
		}
	}

	/**
	 * add content in document
	 * 
	 * @param document					element of PDF
	 * @param content					element of PDF
	 * @throws DocumentException
	 */
	public void addContent(Document document, String content) throws DocumentException {
		if (document != null && !content.isEmpty()) {
			document.add(new Paragraph(content, baseFont));
		}
	}

	/**
	 * add element in chapter
	 * 
	 * @param chapter			element of PDF
	 * @param element			element of PDF
	 */
	public void addContent(Chapter chapter, Element element) {
		if (chapter != null && element != null) {
			chapter.add(element);
		}
	}

	/**
	 * add content in chapter
	 * 
	 * @param chapter			element of PDF
	 * @param content			element of PDF
	 */
	public void addContent(Chapter chapter, String content) {
		if (chapter != null && !content.isEmpty()) {
			chapter.add(new Paragraph(content, baseFont));
		}
	}

	/**
	 * add element in section
	 * 
	 * @param section			element of PDF
	 * @param element			element of PDF
	 */
	public void addContent(Section section, Element element) {
		if (section != null && element != null) {
			section.add(element);
		}
	}

	/**
	 * add content in section
	 * 
	 * @param section			element of PDF
	 * @param content			element of PDF
	 */
	public void addContent(Section section, String content) {
		if (section != null && !content.isEmpty()) {
			section.add(new Paragraph(content, baseFont));
		}
	}

	/**
	 * add pdf Title
	 * 
	 * @param document					element of PDF
	 * @param pdfTitle					title name of PDF
	 * @throws DocumentException
	 */
	public void addPdfTitle(Document document, String pdfTitle) throws DocumentException {
		Paragraph titleParagraph = new Paragraph(pdfTitle, titleFont);
		titleParagraph.setAlignment(Element.ALIGN_CENTER);
		document.add(titleParagraph);
	}

	/**
	 * add directory, there are two page rule, directory and content
	 * 
	 * @param document					element of PDF
	 * @param directoryTitle			directory title name
	 * @throws DocumentException
	 */
	public void addDirectory(Document document, String directoryTitle) throws DocumentException {
		if (document == null) {
			return;
		}

		document.newPage();
		IS_CONTENT_PAGE = false;

		addPdfTitle(document, directoryTitle);

		int toc = pdfWriter.getPageNumber();
		for (Paragraph p : chapterSectionEvent.titles) {
			document.add(p);
		}
		document.newPage();

		int total = pdfWriter.reorderPages(null);
		int[] order = new int[total];
		for (int i = 0; i < total; i++) {
			order[i] = i + toc;
			if (order[i] > total)
				order[i] -= total;
		}
		pdfWriter.reorderPages(order);
	}

	/**
	 * header and footer event, it can generate page, some info .etc
	 * there are two page rule for directory and content.
	 *
	 * @author Hyman Hu
	 * @since  2014-6-23
	 * Copyright SHOP.COM: all rights reserved worldwide
	 */
	class HeaderFooterEvent extends PdfPageEventHelper {
		/*
		 * Initialization text number
		 */
		int textNumber = 1;
		/*
		 * Initialization directory number
		 */
		int directoryNumber = 1;
		/*
		 * default rectangle
		 */
		Rectangle rect = new Rectangle(50f, 50f, 50f, 50f);
		/*
		 * default header content
		 */
		String headerContent = "";

		public int getPageNumber() {
			return textNumber;
		}

		public void setPageNumber(int textNumber) {
			this.textNumber = textNumber;
		}

		public HeaderFooterEvent(Rectangle rect) {
			this.rect = rect;
		}

		/**
		 * @param rect				element of PDF
		 * @param headerContent		header content
		 */
		public HeaderFooterEvent(Rectangle rect, String headerContent) {
			this.rect = rect;
			this.headerContent = headerContent;
		}

		/* (non-Javadoc)
		 * @see com.itextpdf.text.pdf.PdfPageEventHelper#onStartPage(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document)
		 */
		public void onStartPage(PdfWriter writer, Document document) {
			if (PDFCreator.IS_CONTENT_PAGE) {
				textNumber++;
			} else {
				directoryNumber++;
			}
		}

		/* (non-Javadoc)
		 * @see com.itextpdf.text.pdf.PdfPageEventHelper#onEndPage(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document)
		 */
		public void onEndPage(PdfWriter writer, Document document) {
			ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_RIGHT, new Phrase(headerContent),
					rect.getRight(), rect.getTop(), 0);
			if (PDFCreator.IS_CONTENT_PAGE) {
				ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_CENTER,
						new Phrase(String.format("Text Page %d", textNumber)), (rect.getLeft() + rect.getRight()) / 2,
						rect.getBottom() - 18, 0);
			} else {
				ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_CENTER,
						new Phrase(String.format("Directory Page %d", directoryNumber)),
						(rect.getLeft() + rect.getRight()) / 2, rect.getBottom() - 18, 0);
			}
		}
	}

	/**
	 * chapter, section and subsection event
	 *
	 * @author Hyman Hu
	 * @since  2014-6-23
	 * Copyright SHOP.COM: all rights reserved worldwide
	 */
	class ChapterSectionEvent extends PdfPageEventHelper {
		/*
		 * chapter\section\subsection name list, make directory
		 */
		List<Paragraph> titles = new ArrayList<Paragraph>();
		/*
		 * default number for indentation
		 */
		float indentationNumber = 20;
		/*
		 * Initialization text number
		 */
		int textNumber = 1;
		
		Font chapterFont;
		Font sectionFont;
		Font subsectionFont;

		public void setTitles(List<Paragraph> titles) {
			this.titles = titles;
		}

		public void setIndentationNumber(float indentationNumber) {
			this.indentationNumber = indentationNumber;
		}

		public void setTextNumber(int textNumber) {
			this.textNumber = textNumber;
		}

		public void setChapterFont(Font chapterFont) {
			this.chapterFont = chapterFont;
		}

		public void setSectionFont(Font sectionFont) {
			this.sectionFont = sectionFont;
		}

		public void setSubsectionFont(Font subsectionFont) {
			this.subsectionFont = subsectionFont;
		}

		/* (non-Javadoc)
		 * @see com.itextpdf.text.pdf.PdfPageEventHelper#onStartPage(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document)
		 */
		public void onStartPage(PdfWriter writer, Document document) {
			if (PDFCreator.IS_CONTENT_PAGE) {
				textNumber++;
			}
		}

		/* (non-Javadoc)
		 * @see com.itextpdf.text.pdf.PdfPageEventHelper#onChapter(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document, float, com.itextpdf.text.Paragraph)
		 */
		public void onChapter(PdfWriter writer, Document document, float position, Paragraph title) {
			String destination = PDFCreator.ANCHOR_MAP.get(title.getContent());
			titles.add(new Paragraph(new Chunk(getDirectoryLine(title.getContent(), textNumber + ""),
					chapterFont).setLocalGoto(destination)));
		}

		/* (non-Javadoc)
		 * @see com.itextpdf.text.pdf.PdfPageEventHelper#onChapterEnd(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document, float)
		 */
		public void onChapterEnd(PdfWriter writer, Document document, float position) {
			// drawLine(writer.getDirectContent(), document.left(),
			// document.right(), position - 5);
		}

		/* (non-Javadoc)
		 * @see com.itextpdf.text.pdf.PdfPageEventHelper#onSection(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document, float, int, com.itextpdf.text.Paragraph)
		 */
		public void onSection(PdfWriter writer, Document document, float position, int depth, Paragraph title) {
			boolean isSubsection = false;
			String titleContent = title.getContent();
			String destination = PDFCreator.ANCHOR_MAP.get(titleContent);
			String theNo = titleContent.substring(0, titleContent.indexOf(" "));
			if (theNo.indexOf(".") != theNo.lastIndexOf(".")) {
				isSubsection = true;
			}
			if (isSubsection) {
				title = new Paragraph(new Chunk(getDirectoryLine(title.getContent(), textNumber + ""),
						subsectionFont).setLocalGoto(destination));
			} else {
				title = new Paragraph(new Chunk(getDirectoryLine(title.getContent(), textNumber + ""),
						sectionFont).setLocalGoto(destination));
			}
			title.setIndentationLeft(indentationNumber * depth);
			titles.add(title);
		}

		/* (non-Javadoc)
		 * @see com.itextpdf.text.pdf.PdfPageEventHelper#onSectionEnd(com.itextpdf.text.pdf.PdfWriter, com.itextpdf.text.Document, float)
		 */
		public void onSectionEnd(PdfWriter writer, Document document, float position) {
			// drawLine(writer.getDirectContent(), document.left(),
			// document.right(), position - 3);
		}

		/**
		 * draw line
		 * 
		 * @param cb		PdfContentByte, the element of PDF
		 * @param x1		line begin position 
		 * @param x2		line end position
		 * @param y			float number
		 */
		public void drawLine(PdfContentByte cb, float x1, float x2, float y) {
			cb.moveTo(x1, y);
			cb.lineTo(x2, y);
			cb.stroke();
		}

		/**
		 * this is make line for directory
		 * 
		 * @param titleName			chapter\section\subsection name
		 * @param pageNumber		page number
		 * @return
		 */
		public String getDirectoryLine(String titleName, String pageNumber) {
			int total = titleName.length() + pageNumber.length();
			StringBuffer sb = new StringBuffer();
			for (int i = 0; i < 80 - total; i++) {
				sb.append(".");
			}
			return String.format("%s%s%s", titleName, sb.toString(), pageNumber);
		}
	}
}
